package ch.zhaw.cctd.domain.tower;

import java.awt.Dimension;
import java.awt.Point;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Stack;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.zhaw.cctd.domain.GraphicalElement;
import ch.zhaw.cctd.domain.Shootable;
import ch.zhaw.cctd.domain.Shooter;
import ch.zhaw.cctd.domain.map.Map;
import ch.zhaw.cctd.domain.map.MapElement;
import ch.zhaw.cctd.domain.player.Player;
import ch.zhaw.cctd.domain.player.RessourceException;
import ch.zhaw.cctd.domain.prototype.TowerPrototype;
import ch.zhaw.cctd.domain.prototype.TowerPrototype.TowerEffectPrototype;
import ch.zhaw.cctd.domain.prototype.TowerUpgradePrototype;
import ch.zhaw.cctd.domain.shot.Shot;

/**
 * Representiert ein Tower auf der Map. Ist in der Lage auf vorher eingestellte
 * Targets zu schiessen und sich selbst zu Upgraden.
 * 
 * @author Rolf
 * 
 */
public class Tower extends MapElement implements Shooter, GraphicalElement, Serializable {

	private static final long serialVersionUID = 1078559610520442424L;

	private static final Logger logger = LoggerFactory.getLogger(Tower.class);
	private static final int TOWER_LAYER_LEVEL = 0;

	private transient Stack<TowerUpgradePrototype> upgradeprototypes;
	private String title;
	private String type;
	private transient List<TowerEffect> effectList = new ArrayList<TowerEffect>();
	private transient List<String> abilityList = new ArrayList<String>();
	private transient int damage;
	private transient int speed;
	private int range;
	private transient int level = 0;
	private transient Player owner;

	private transient Map map;
	
	//Shot
	private transient int shotSpeed;
	private transient int shotXSize;
	private transient int shotYSize;
	private transient String shotImagePath;
	
	//Statistics
	private transient int statsShots;
	private transient int statskills;

	private transient Shootable target;
	private transient long lastshot;

	/**
	 * Default Constructor
	 * 
	 * @param towerPrototype
	 * @param position
	 * @param owner
	 * @param match
	 */
	public Tower(TowerPrototype towerPrototype, Point position, Player owner, Map map) {
		super(position, new Dimension(towerPrototype.getSize(), towerPrototype.getSize()), towerPrototype.getImagePath(), TOWER_LAYER_LEVEL);
		this.type = towerPrototype.getId();
		this.title = towerPrototype.getTitle();
		this.owner = owner;
		this.map = map;

		this.addEffects(towerPrototype.getEffects());
		this.abilityList = towerPrototype.getAbilities();

		this.damage = towerPrototype.getDamage();
		this.speed = towerPrototype.getSpeed();
		this.range = towerPrototype.getRange();

		this.upgradeprototypes = new Stack<TowerUpgradePrototype>();
		List<TowerUpgradePrototype> sorted = towerPrototype.getUpgrades();
		Collections.sort(sorted);
		this.upgradeprototypes.addAll(sorted);
		this.lastshot=0;
		
		//Shot
		this.shotSpeed = towerPrototype.getShotSpeed();
		this.shotXSize = towerPrototype.getShotXSize();
		this.shotYSize = towerPrototype.getShotYSize();
		this.shotImagePath = towerPrototype.getShotImagePath();
	}

	/**
	 * Upgrade the Tower to the next Level
	 * 
	 * @throws RessourceException
	 */
	public void upgrade() throws RessourceException {
		TowerUpgradePrototype prototype = this.upgradeprototypes.firstElement();
		if (owner.hasRessource(prototype.getCost())) {
			// do upgrade
			this.setTexture(prototype.getImagePath());
			this.abilityList.addAll(prototype.getAbilities());
			this.addEffects(prototype.getAddedEffects());
			this.damage += prototype.getAddedDamage();
			this.range += prototype.getAddedRange();
			this.speed += prototype.getAddedSpeed();
			this.level++;
			owner.payRessource(prototype.getCost());
		} else {
			throw new RessourceException("Not enoug money for Upgrade");
		}

	}

	/**
	 * Tower Title
	 * 
	 * @return title
	 */
	public String getTitle() {
		return this.title;
	}

	/**
	 * Tower Type String
	 * 
	 * @return Type
	 */
	public String getType() {
		return this.type;
	}

	/**
	 * Checks if the Tower has Effects
	 * 
	 * @param effectname
	 * @return boolean
	 */
	public boolean hasEffect(String name) {
		return this.effectList.contains(name);
	}

	/**
	 * Get a Tower Effect List
	 * 
	 * @return
	 */
	public List<TowerEffect> getEffectList() {
		return effectList;
	}

	/**
	 * Add Tower Effects by a TowerEffect Prototype List
	 * 
	 * @param effects
	 */
	private void addEffects(List<TowerEffectPrototype> effects) {
		for (TowerPrototype.TowerEffectPrototype t : effects) {
			effectList.add(TowerEffect.getTowerEffectOfType(t));
		}
	}

	/**
	 * Get Ability List
	 * 
	 * @return
	 */
	public List<String> getAbilityList() {
		return abilityList;
	}

	/**
	 * Get Tower Level
	 * 
	 * @return
	 */
	public int getLevel() {
		return level;
	}

	/**
	 * Increment the Level
	 */
	void incrementLevel() {
		this.level++;
	}

	/**
	 * Get Tower Firing Speed in ms
	 * 
	 * @return
	 */
	public int getSpeed() {
		return speed;
	}

	/**
	 * Get Range in Pixel
	 */
	public int getRange() {
		return range;
	}

	/**
	 * Get Damage Done
	 * 
	 * @return damageint
	 */
	public int getDamage() {
		return this.damage;
	}

	/**
	 * Get the Owner of the Tower
	 */
	public Player getOwner() {
		return this.owner;
	}

	/* state machine */
	/**
	 * Sets a possible Shooting Target
	 */
	public void setTarget(Shootable s) {
		this.target = s;
	}

	/**
	 * Gets the possible Shooting Target
	 * 
	 * @return
	 */
	public Shootable getTarget() {
		return this.target;
	}

	/**
	 * Checks if the Tower has a Target
	 */
	public boolean hasTarget() {
		if (this.target == null)
			return false;
		if (!this.target.isAlive() || this.getCenter().distance(this.target.getCenter()) > this.range) {
			this.target = null;
		}
		return this.target != null;
	}

	/**
	 * Shoot the Target if possible
	 */
	public void shoot() {
		if ((this.lastshot==0 || this.lastshot + this.speed * 1000 < Calendar.getInstance().getTimeInMillis()) && this.hasTarget()) {
			logger.trace("Shot"  );
			//FIXME: Dont make this Hardcoded!
			map.addShot(new Shot(this.getCenter(), new Dimension(this.shotXSize,this.shotYSize), this.shotImagePath, this.shotSpeed, this, this.getTarget()));
			lastshot = Calendar.getInstance().getTimeInMillis();
			statsShots++;
		}
	}
	
	public void incStatsKill() {
		statskills++;
	}
	public int getKills() {
		return statskills;
	}
	public int getShots() {
		return statsShots;
	}

	@Override
	public void targetHit(Point hitpoint) {
		for (TowerEffect effect : this.effectList) {
			effect.hitEffect(hitpoint, this, this.getTarget(), this.map);
		}
	}

}
